/**
 * Abstract class definition of a packet in the EGS system
 *
 * @author Jonathan Walsh (jwalsh8484@gmail.com)
 */

package egs.packets;

import java.nio.ByteBuffer;

public abstract class Packet {
    
    /* Protocol version for EGS we are implementing */
    public static final short PROTOCOL_VERSION = 1;

    /* Message type values */
    public static final short MSG_USE_SECURE = 1;
    public static final short MSG_USE_SECURE_RESPONSE = 2;
    public static final short MSG_AUTH_LOGIN = 3;
    public static final short MSG_AUTH_LOGIN_ERR = 4;
    public static final short MSG_GAME_LIST = 5;
    public static final short MSG_SELECT_GAME = 6;
    public static final short MSG_INVALID_GAME = 7;
    public static final short MSG_OPP_LIST = 8;
    public static final short MSG_OPP_CHOICE = 9;
    public static final short MSG_OPP_OFFER = 10;
    public static final short MSG_OPP_NACK = 11;
    public static final short MSG_OPP_ACK = 12;
    public static final short MSG_BOARD_SEND = 13;
    public static final short MSG_MOVE_SEND = 14;
    public static final short MSG_MOVE_RESPONSE = 15;
    public static final short MSG_END_GAME = 16;
    public static final short MSG_DISCONNECT = 17;
    
    /* Game name value registry */
    public static final short GAME_NAME_CHECKERS = 1;
    public static final short GAME_NAME_BLACKJACK = 2;
    public static final short GAME_NAME_TICTACTOE = 3;

    /* Header values */
    protected short version = PROTOCOL_VERSION;
    protected short message_type = 0;
    protected short game_name = 0;
    protected short game_version = 0;
    protected int data_area_size = 0;

    /*Some helpful constants*/
    public static final int HEADER_SIZE = 4*3;

    /* Getters and setters for header values */
    public short get_version() { return version; }
    public void set_version( short version ) { this.version = version; }

    public short get_message_type() { return message_type; }
    public void set_message_type( short message_type ) { this.message_type = message_type; }

    public short get_game_name() { return game_name; }
    public void set_game_name( short game_name ) { this.game_name = game_name; }
    
    public short get_game_version() { return game_version; }
    public void set_game_version( short game_version ) { this.game_version = game_version; }

    public int get_data_area_size() { return data_area_size; }
    public void set_data_area_size( int data_area_size ) { this.data_area_size = data_area_size; }

    /*
     * Packet helper function which generates the padded byte array 
     * representing the header values
     */
    protected byte[] makeHeader() {
        ByteBuffer buf = ByteBuffer.allocate(HEADER_SIZE);
        
        buf.putShort( 0, version );
        buf.putShort( 2, message_type );
        buf.putShort( 4, game_name );
        buf.putShort( 6, game_version );
        buf.putInt( 8, data_area_size );
        
        return buf.array();
    }
    
    /*
     * Apply the header values, and return the ByteBuffer with the rest of the
     * packet, and the marker moved to the end of the header for future
     * relative data operations.
     *
     * @param raw - The raw byte array from the socket
     */
    protected ByteBuffer setHeaderNew( byte[] raw ) {
        ByteBuffer buf = ByteBuffer.wrap( raw );
        
        version = buf.getShort();
        message_type = buf.getShort();
        game_name = buf.getShort();
        game_version = buf.getShort();
        data_area_size = buf.getInt();	
        
        return buf;
    }

    /*
     * Returns a ByteBuffer with the header filled in and the marker moved to
     * the end of the header for future relative data operations.
     *
     * @return data_area_size - The size of the data area
     */
    protected ByteBuffer makeBufferWithHeader( int data_area_size ) {
        ByteBuffer buf = ByteBuffer.allocate(HEADER_SIZE+data_area_size);
        
        buf.putShort( version );
        buf.putShort( message_type );
        buf.putShort( game_name );
        buf.putShort( game_version );
        buf.putInt( data_area_size );
        
        return buf;
    }

    /*
     * Helper function to pull the protocol_version out of the raw packet\
     *
     * @param raw - The raw packet data
     * @return The protocol version from the raw packet data
     */
    public static short get_protocol_version( byte[] raw ) {
        return ByteBuffer.wrap( raw ).getShort( 0 );
    }

    /*
     * Helper function to pull the message_type out of the raw packet
     *
     * @param raw - The raw packet data
     * @return The protocol message_type from the raw packet data
     */
    public static short get_message_type( byte[] raw ) {
        return ByteBuffer.wrap( raw ).getShort( 2 );
    }

    public static int get_data_area_size( byte[] raw ) {
        return ByteBuffer.wrap( raw ).getInt( 8 );
    }
    
    /*
     * Abstract function which must be implemented by any subclasses.  This 
     * function is used to build the data to send over the wire for the 
     * specific packet.
     *
     * @return Raw byte array to send on the wire.
     */
    public abstract byte[] getRaw();

    /*
     * Returns a string representation of this packet.  Since this is an
     *  abstract class, this is used by subclasses to print the header part
     *  of the packet for debugging.
     * 
     * @return String representation of the header
     */
    public String toString() {
        String ret = "version = " + version + "\tmessage_type = " + message_type;
        ret = ret + "\ngame_name = " + game_name + "\tgame_version = " + game_version;
        ret = ret + "\ndata_area_size = " + data_area_size;
        return ret;
    }




    /*
     * Function to get the next valid packet from a byte array (read off the
     *  socket).  Invalid packets will be stripped from the buffer.  This
     *  function assumes some reliable mechanism ensures that the first byte
     *  in the array is the start of the next packet.
     *
     * @param raw - The raw byte buffer to strip a packet from
     * @return The next valid packet or null for no packets
     */
    public static Packet getNextPacket( byte[] raw ) {
        int dataLen = raw.length;

        /* Need at least the header */
        if ( dataLen <= HEADER_SIZE ) return null;
        
        /*First pull the data size to make sure we have enough bytes for this
          packet type */
        int data_amount = HEADER_SIZE + get_data_area_size( raw );
        
        /*this packet was fragged, return so the receiever can get the rest*/
        if ( dataLen < data_amount ) {
            return null;
        }
        
        /*Chunk out that part of the byte array*/
        //TODO MAKE THIS LESS DUMB
        byte praw[] = new byte[data_amount];
        for ( int i = 0; i < data_amount; i++ ) praw[i] = raw[i];
               
        
        /*Make sure this protocol version is the same as ours*/
        if ( get_protocol_version( raw ) != PROTOCOL_VERSION ) return null;
        
        /*get the message type, and construct the correct packet based on that*/
        short message_type = get_message_type( praw );
        
        Packet retp = null;
        switch ( message_type ) {
            
        case MSG_USE_SECURE:
            retp = new UseSecurePacket( praw );
            break;
        case MSG_USE_SECURE_RESPONSE:
            retp = new UseSecureResponsePacket( praw );
            break;
        case MSG_AUTH_LOGIN:
            retp = new AuthLoginPacket( praw );
            break;
        case MSG_AUTH_LOGIN_ERR:
            retp = new AuthLoginErrPacket( praw );
            break;
        case MSG_GAME_LIST:
            retp = new GameListPacket( praw );
            break;
        case MSG_SELECT_GAME:
            retp = new SelectGamePacket( praw );
            break;
        case MSG_INVALID_GAME:
            retp = new InvalidGamePacket( praw );
            break;
        case MSG_OPP_LIST:
            retp = new OppListPacket( praw );
            break;
        case MSG_OPP_CHOICE:
            retp = new OppChoicePacket( praw );
            break;
        case MSG_OPP_OFFER:
            retp = new OppOfferPacket( praw );
            break;
        case MSG_OPP_NACK:
            retp = new OppNackPacket( praw );
            break;
        case MSG_OPP_ACK:
            retp = new OppAckPacket( praw );
            break;
        case MSG_BOARD_SEND:
            retp = new BoardSendPacket( praw );
            break;
        case MSG_MOVE_SEND:
            retp = new MoveSendPacket( praw );
            break;
        case MSG_MOVE_RESPONSE:
            retp = new MoveResponsePacket( praw );
            break;
        case MSG_END_GAME:
            retp = new EndGamePacket( praw );
            break;
        case MSG_DISCONNECT:
        	retp = new DisconnectPacket( praw );
        	break;
        default:
            /*Invalid message type, drop it!*/
            System.out.println( "Invalid message_type read: " + message_type );
        }
        
        return retp;
    }
}
